package il.technion.cs236369.dal;

import il.technion.cs236369.exceptions.NotFoundException;
import il.technion.cs236369.interfaces.ICacheEntry;
import il.technion.cs236369.interfaces.ICacheEntryDal;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;

/**
 * A DAL implementation
 * 
 * @author Gal Lalouche 036933489 sgallal@t2
 * 
 */
class CacheEntryDalImpl implements ICacheEntryDal {

	private static final String	HEADER_DELIMITER	= "\r\n";
	private static final String	KEY_VALUE_SPLITTER	= "! @ !";

	private static final String	URL_KEY				= "url";
	private static final String	BODY_KEY			= "body";
	private static final String	HEADERS_KEY			= "headers";

	private final String		_url;
	private final String		_dbName;
	private final String		_userName;
	private final String		_password;
	private final String		_tableName;

	private Connection			_connection;						// if null, connection is closed

	public CacheEntryDalImpl(String driver, String url, String dbName, String tableName, String userName, String password)
			throws ClassNotFoundException, SQLException {
		Class.forName(driver);

		_url = url;
		_dbName = dbName;
		_userName = userName;
		_password = password;
		_tableName = tableName;
		// check that connection can be established
		openConnection();
		closeConnection();

		// assume table is already created
	}

	// opens a new connection if one is not already open
	private boolean openConnection() throws SQLException {
		if (_connection != null) {
			return false;
		}

		_connection = DriverManager.getConnection(_url + _dbName, _userName, _password);
		return true;
	}

	// closes the connection if it is not closed
	private boolean closeConnection() throws SQLException {
		if (_connection == null) {
			return false;
		}

		_connection.close();
		_connection = null;
		return true;
	}

	// executes a given query that returns no results
	private void execute(String query) throws SQLException {
		Statement statement = _connection.createStatement();
		statement.execute(query);
	}

	// executes a given query and returns the results 
	private ResultSet executeQuery(String query) throws SQLException {
		assert _connection != null;
		Statement statement = _connection.createStatement();
		return statement.executeQuery(query);
	}

	// insert a new row in to the DB
	private void insert(String url, String headers, byte[] body) throws SQLException {
		openConnection();
		String sb = "insert into " + _tableName + " values (?, ?, ?)";
		PreparedStatement pst = _connection.prepareStatement(sb);
		pst.setString(1, url);
		pst.setString(2, headers);
		pst.setBytes(3, body);
		pst.execute();
		closeConnection();
	}

	@Override
	public boolean store(ICacheEntry entry) throws SQLException {
		boolean entryAlreadyExists = contains(entry.getURL());
		if (entryAlreadyExists) {
			boolean result = remove(entry.getURL());
			assert result;
		}

		StringBuilder headers = new StringBuilder();
		for (Map.Entry<String, String> mapEntry : entry.getHeaders().entrySet()) {
			headers.append(mapEntry.getKey() + KEY_VALUE_SPLITTER + mapEntry.getValue() + HEADER_DELIMITER);
		}
		insert(entry.getURL().toString(), headers.toString(), entry.getBody());
		return entryAlreadyExists;
	}

	@Override
	public ICacheEntry load(String url) throws NotFoundException, SQLException {
		openConnection();
		ResultSet rs = executeQuery("select * from " + _tableName + " where " + URL_KEY + " = '" + url.toString() + "'");
		if (rs.next() == false) {
			throw new NotFoundException(url.toString());
		}

		Map<String, String> headers = new HashMap<String, String>();
		String[] headersStrings = rs.getString(HEADERS_KEY).split(HEADER_DELIMITER);
		for (String nextHeader : headersStrings) {
			String[] splitHeader = nextHeader.split(KEY_VALUE_SPLITTER);
			headers.put(splitHeader[0], splitHeader[1]);
		}

		CacheEntryImpl result = new CacheEntryImpl(url, headers, rs.getBytes(BODY_KEY));
		return result;
	}

	@Override
	public boolean contains(String url) throws SQLException {
		openConnection();
		ResultSet result = executeQuery("select * from " + _tableName + " where " + URL_KEY + " = '" + url.toString() + "'");
		boolean hasNext = result.next();
		closeConnection();
		return hasNext;

	}

	@Override
	public void clear() throws SQLException {
		openConnection();
		execute("delete from " + _tableName);
		closeConnection();
	}

	@Override
	public boolean remove(String url) throws SQLException {
		boolean doestEntryExist = contains(url);
		openConnection();
		execute("delete from " + _tableName + " where " + URL_KEY + " = '" + url.toString() + "'");
		closeConnection();
		return doestEntryExist;
	}

}
